Client-Client間のRPCのここが嫌だ


概要

iOSとかで動く対戦型で二人以上のプレイヤーが動くゲームでのClient-Client間のRPCは使いどころ無いので死ねみたいな話。

主にself-definedでないRPCについての文句。


Remote Procedure Call

定義

メソッドシグニチャとかを送って他Peerのメソッドをぶっ叩くこと。


結論から言うと、P2Pでない対戦ゲームでClient-ClientのRPCを使うべきでは無いと思っている。

代表的な嫌なことは下記。代案もある。



Version差で、Client-Serverが引きずられる

独自定義した「わけではない」バイナリを送る感じになるので、クライアント間でのバージョン互換性を保持しないとゲームにならない。

= 全ユーザーのバージョンを保たないといけない

= サーバ側もその制約を被る。

-> いろんなバージョンのクライアントの面倒をみる羽目になる

それは詰み。



データサイズが制御できない

結論から言うとデータ形式をゲームごとに自分で定義したほうがいい。


行動コード + 発行者コード + … とか、すべてbyteで書き、できるだけサイズを小さくする。

っていうのが理想だけど、


開発中は別に中身がJSONとかでも良いと思う。最終的にサイズを小さく、

Clientから送るものはフラグだけ、とかになる。


Serverが送り出すものは、Serverで計算なり発行を決めたデータ になる。

つまりRPCな必要が無い。

ゲームごとに制御したい物事、そのインターバルは異なる。

間違っても「定義されているものを使う」という発想にならないほうがいい。

首が絞まってから何かするのは避けてほしい。



Client-Client間でのRPCは、サーバでの統一がしにくい

Client-ClientでRPCを行うのは以下のようなケース。

1.Client A が歩いた

2.Client Aは、歩いたコマンドを発行(コマンドの対象、タイミングはこの時決まる)

3.コマンドがサーバに到達

4.サーバはClient A,B,C,Dに対して、Aが動いたということを通知

5.Client A,B,C,Dのメソッドが着火され、Client Aが動いたのと同じ状態を作り出す


これは、サーバが持つべき「ゲームの統一動作」という責務に対して、最悪の結果を生む。

全Clientがコマンドを受け取ってダイレクトに実行してしまう形になる。


例えば以下の要件は、本来、すべてサーバが担うべきもの。

・Clientが受け取るコマンドの種類を決める

・コマンドの実行可否を決める

・コマンドの実行タイミングを決める

・コマンドの対象を決める


これをClient主導で行うのがなぜダメか。Serverに実行可能かどうかの判断をする場所がなく、実行中の状態が把握できないからだ。


動かす物事のバランスは、「Serverをまずめっちゃ重く」「Clientを軽く」から始めた方がバランスしやすい

最初は計算や描画など、Serverにできない、重たいことをClientにやらせる。

最初からすべてをClientにやらせると、あとでServerにその実装を移す際に困る。

・ClientどうしでRPCするということは、Serverで何もしていない = Serverになんの実装も無い、ということ

・その処理に同期的な処理(アイテム取るとか移動とか攻撃とかダメージとか要はすべて)が必要になった時、ClientがRPCを止めるのが大変。


Client同士でやっていたことを、Serverが行う必要がでてくるが、その実装はゼロから書く羽目になる。


逆にServerが重い状態から始まれば、「チートされても問題の無い箇所」に対して、どこをClientで行うか、という分散はしやすい。

もともとServerでやらないでもいいこと、というのをClientに持ってくるのが正しい。


ほら、RPC無駄だろ?みたいな。

まあ理想論なんだけど。実際には、全部Server側で実装してから~みたいな無駄なことはしないでいいと思う。ただServer側に倒しておくと、あとで救われる。



まだあるRPCの弊害

・チート対策がしづらい

byteで「このClientで実行した」っていうデータを発行してしまうので、偽造されたら一発。

Serverで状態をもっていれば、「この状態だったらこの入力があってもNをしてる最中なので無視!!」とかができる。

・サイズが無駄にでかい

フラグだったら1bitで済むところ、RPCのシリアライゼーションデータを丸っと送る価値って何。


・途中介入がしづらい

Serverが定義していて把握できるbyte列であれば、「先頭数byteをみれば何だかわかる」という条件があると楽。

だが、これはRPCじゃあないよね。

つまりRPCではなく自分でデザインした方がいいよって話。


・実行がメソッド単位で行われてしまう

APIを組み替える時に常にブービートラップのように事件が起こる。

ダイレクトにClientたちの動作が変更されてしまう。仲介役であるServerを絡ませた方がいい。



一度体験すればRPCがどのくらいクソかわかると思うけど、体験させないとダメ?

他のゲームがどうやってるか、どんなFutureを考えているかを見ることで、

ClientベースのRPCが使い物にならない(=実際に使って良いシーンは無い)ことがわかると思う。

できるだけ無駄な体験に時間を費やしたく無い。



じゃあRPCではなくどうすれば良いのか

Server側で、インターバルを持って物事を処理する。


例えばおしくらまんじゅう有りの、位置が同期するゲームの場合は、以下のようなパターンになる。


A:厳格なタイプ

・Clientがフラグを送る(歩く)

・Client側でそのまま歩くモーションになってもいい

・Server側がフラグを受け取る

・Server側のフレームレートで、各プレイヤーから受け取ったフラグを回収

・Server側で一回のフラグに付きどのくらい歩くかを計算、ぶつかりとかもここで計算

・ServerからコマンドをClientに発行

・各Clientがコマンドを受け取り、移動を実行

Server側にすべての計算結果が残るので、再現も中断も楽勝。

加えてキャラクター間のぶつかりとかも計算できる。

敵が死ぬとかはこのフェーズで行わないと困る。



B:あんま厳格でないが速い、失敗しても立て直すタイプ

・Clientがフラグを送る

・Server側がフラグを受け取る

・Serverはフラグから可否のみを計算、フラグを各Clientにできるだけ速く返す

・Serverは裏でClientと同じ計算をする

・Clientはフラグを受け取って動く

・Serverから答え合わせのフレームデータを各Clientに送る

・Clientはそれを受け取って事実を捻じ曲げ再現する

Server側には時間差込みで計算結果が残り、かつ最速で動くことができる。

プレイヤーが少ないほどより効率ダメージを受けず、効果が出る。



実際には、AとBのミックスで設計することが多い。


Client->Server間のRPCについて

フラグ送付ができればいいので、要らないでしょ

それRPCじゃないほうが後々ラクだよね。


なにが悲しくてServer側のメソッドの情報をClientに持たなければいけないのか。



Server->Client間のRPCについて

ClientのVersionに合わせたRPC書くの?

いらなくね?



こちらからは以上です。